/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { ITypeScriptServiceClient } from '../typescriptService';
import * as languageIds from '../utils/languageModeIds';

namespace FormattingConfiguration {
	export function equals(a: any, b: any): boolean {
		let keys = Object.keys(a);
		for (let i = 0; i < keys.length; i++) {
			let key = keys[i];
			if ((a as any)[key] !== (b as any)[key]) {
				return false;
			}
		}
		return true;
	}
}

export default class FormattingConfigurationManager {
	private onDidCloseTextDocumentSub: any | undefined;
	private formatOptions: { [key: string]: any | undefined; } = Object.create(null);

	public constructor(
		private client: ITypeScriptServiceClient,
		private vscode: any
	) {
		this.onDidCloseTextDocumentSub = vscode.workspace.onDidCloseTextDocument((textDocument: any) => {
			const key = textDocument.uri.toString();
			// When a document gets closed delete the cached formatting options.
			// This is necessary since the tsserver now closed a project when its
			// last file in it closes which drops the stored formatting options
			// as well.
			delete this.formatOptions[key];
		});
	}

	public dispose() {
		if (this.onDidCloseTextDocumentSub) {
			this.onDidCloseTextDocumentSub.dispose();
			this.onDidCloseTextDocumentSub = undefined;
		}
	}

	public async ensureFormatOptionsForDocument(
		document: any,
		token: any | undefined
	): Promise<void> {
		const editor = this.vscode.window.visibleTextEditors.find((editor: any) => editor.document.fileName === document.fileName);
		if (editor) {
			const formattingOptions = {
				tabSize: editor.options.tabSize,
				insertSpaces: editor.options.insertSpaces
			};
			return this.ensureFormatOptions(document, formattingOptions, token);
		}
	}

	public async ensureFormatOptions(
		document: any,
		options: any,
		token: any | undefined
	): Promise<void> {
		const file = this.client.normalizePath(document.uri);
		if (!file) {
			return;
		}

		const key = document.uri.toString();
		const cachedOptions = this.formatOptions[key];
		const formatOptions = this.getFormatOptions(document, options);

		if (cachedOptions && FormattingConfiguration.equals(cachedOptions, formatOptions)) {
			return;
		}

		const args = {
			file: file,
			formatOptions: formatOptions
		};
		await this.client.execute('configure', args, token);
		this.formatOptions[key] = formatOptions;
	}

	public reset() {
		this.formatOptions = Object.create(null);
	}

	private getFormatOptions(
		document: any,
		options: any
	): any {
		const config = this.vscode.workspace.getConfiguration(
			document.languageId === languageIds.typescript || document.languageId === languageIds.typescriptreact
				? 'typescript.format'
				: 'javascript.format',
			document.uri);
		return {
			tabSize: options.tabSize,
			indentSize: options.tabSize,
			convertTabsToSpaces: options.insertSpaces,
			// We can use \n here since the editor normalizes later on to its line endings.
			newLineCharacter: '\n',
			insertSpaceAfterCommaDelimiter: config.get('insertSpaceAfterCommaDelimiter'),
			insertSpaceAfterConstructor: config.get('insertSpaceAfterConstructor'),
			insertSpaceAfterSemicolonInForStatements: config.get('insertSpaceAfterSemicolonInForStatements'),
			insertSpaceBeforeAndAfterBinaryOperators: config.get('insertSpaceBeforeAndAfterBinaryOperators'),
			insertSpaceAfterKeywordsInControlFlowStatements: config.get('insertSpaceAfterKeywordsInControlFlowStatements'),
			insertSpaceAfterFunctionKeywordForAnonymousFunctions: config.get('insertSpaceAfterFunctionKeywordForAnonymousFunctions'),
			insertSpaceBeforeFunctionParenthesis: config.get('insertSpaceBeforeFunctionParenthesis'),
			insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: config.get('insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis'),
			insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: config.get('insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets'),
			insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces'),
			insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces'),
			insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces'),
			insertSpaceAfterTypeAssertion: config.get('insertSpaceAfterTypeAssertion'),
			placeOpenBraceOnNewLineForFunctions: config.get('placeOpenBraceOnNewLineForFunctions'),
			placeOpenBraceOnNewLineForControlBlocks: config.get('placeOpenBraceOnNewLineForControlBlocks'),
		};
	}
}
